using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;

using BizHawk.Common.PathExtensions;

namespace BizHawk.Common
{
	/// <summary>
	/// Starts a thread which cleans any filenames in %temp% beginning with bizhawk.bizdelete.
	/// Files shouldn't be named that unless they're safe to delete, but notably, they may still be in use. That won't hurt this component.
	/// When they're no longer in use, this component will then be able to delete them.
	/// </summary>
	public static class TempFileManager
	{
		// TODO - manage paths other than %temp%, make not static, or allow adding multiple paths to static instance

		public static string GetTempFilename(string friendlyName, string? dotAndExtension = null, bool delete = true)
		{
			var fname = $"biz-{Process.GetCurrentProcess().Id}-{friendlyName}-{Util.GetRandomUUIDStr()}{dotAndExtension ?? ""}";
			if (delete)
			{
				fname = RenameTempFilenameForDelete(fname);
			}

			return Path.Combine(Path.GetTempPath(), fname);
		}

		/// <exception cref="InvalidOperationException">filename in <paramref name="path"/> is not one generated by <see cref="GetTempFilename"/></exception>
		public static string RenameTempFilenameForDelete(string path)
		{
			var (dir, filename) = path.SplitPathToDirAndFile();
			_ = dir ?? throw new InvalidOperationException();
			if (!filename.StartsWith("biz-", StringComparison.Ordinal))
			{
				throw new InvalidOperationException();
			}

			filename = $"bizdelete-{filename.Remove(0, 4)}";
			return Path.Combine(dir, filename);
		}

		public static void Start()
		{
			lock (typeof(TempFileManager))
			{
				if (thread != null)
				{
					return;
				}

				thread = new Thread(ThreadProc)
				{
					IsBackground = true,
					Priority = ThreadPriority.Lowest,
				};
				thread.Start();
			}
		}

		private static void ThreadProc()
		{
			// squirrely logic, trying not to create garbage
			var knownTempDirs = new HashSet<string>();
			var dis = new List<DirectoryInfo>();
			for (;;)
			{
				lock (typeof(TempFileManager))
				{
					knownTempDirs.Add(Path.GetTempPath());
					if (dis.Count != knownTempDirs.Count)
						dis = knownTempDirs.Select(x => new DirectoryInfo(x)).ToList();
				}

				foreach(var di in dis)
				{
					FileInfo[] fis;
					try
					{
						fis = di.GetFiles("bizdelete-*");
					}
					catch
					{
						continue;
					}

					foreach (var fi in fis)
					{
						try
						{
							if (OSTailoredCode.IsUnixHost)
							{
								fi.Delete(); // naive deletion, Mono doesn't care
							}
							else
							{
								Win32Imports.DeleteFileW(fi.FullName); // SHUT. UP. THE. EXCEPTIONS.
							}
						}
						catch
						{
						}

						// try not to do more than one thing per frame
						Thread.Sleep(100);
					}
				}

				// try not to slam the filesystem too hard, we don't want this to cause any hiccups
				Thread.Sleep(5000);
			}
		}

		public static void Stop()
		{
		}

		private static Thread? thread;

		public static void HelperSetTempPath(string path)
		{
			// yes... this is how we're doing it, for now, until it's proven to be troublesome
			Directory.CreateDirectory(path);
			Environment.SetEnvironmentVariable("TMPDIR", path);
			Environment.SetEnvironmentVariable("TMP", path);
			Environment.SetEnvironmentVariable("TEMP", path);
			Environment.SetEnvironmentVariable("TEMPDIR", path);
		}
	}
}
